package org.vectrics.web.menu;

import java.io.File;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Logger;
import org.vectrics.SystemException;
import org.vectrics.common.util.StringUtils;
import org.vectrics.domain.Domain;
import org.vectrics.provider.CoreServiceLocator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Class to load menus from configuration files or other XML.
 * 
 * @author Mike Moore
 * 
 */
public class MenuConfigReader {
    private final Logger log = Logger.getLogger(this.getClass());

    /**
     * Load Menu object from configuration file
     * 
     * @param fileName
     *            The configuration file
     * @return
     */
    public Menu loadFile(File file) {
        String fileName = file.getPath();
        Menu menu = null;
        try {
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder parser = factory.newDocumentBuilder();
                Document document = null;

                try {
                    document = parser.parse(file);
                    menu = this.loadFromXml(document.getDocumentElement());
                } catch (SAXException se) {
                    throw new SystemException("SAX Exception parsing content", se);
                }

            } catch (javax.xml.parsers.ParserConfigurationException pe) {
                throw new SystemException("Parser exception during config read. " + pe.getMessage(), pe);
            } catch (Exception ex) {
                this.log.error("Exception parsing XML content: " + fileName, ex);
                throw new SystemException("Excpeption parsing config file: " + fileName, ex);
            }
        } catch (Exception ex) {
            this.log.error("Exception opening file: " + fileName, ex);
            throw new SystemException("Exception opening file: " + fileName, ex);
        }
        return (menu);
    }

    /**
     * Load Menu object from XML element. Assumes that element being passed is
     * parent of 'menu' elements.
     * 
     * @param configElement
     * @return
     */
    public Menu loadFromXml(Element configElement) {
        Menu menu = this.readMenuElement(configElement);
        return (menu);
    }

    /**
     * Load menu from default configuration file for domain and code.
     * 
     * @param domain
     *            The Data Owner or domain
     * @param code
     *            The code assigned to the menu.
     * @return The Menu object populated with sub-items, etc..
     */
    public Menu loadMenu(Domain domain, String code) {
        File menuFile = new File(CoreServiceLocator.getConfiguration().getMenuConfigDirectory(domain), code + ".xml");
        return (this.loadFile(menuFile));
    }

    private Menu readMenuElement(Element menuElement) {
        String id = menuElement.getAttribute("id");
        if (this.log.isDebugEnabled()) {
            this.log.debug("Adding menu definition - id = " + id);
        }
        Menu root = new Menu();
        root.setId(id);
        if (this.log.isDebugEnabled()) {
            this.log.debug("readMenuElement() - Parsing menu: " + id);
        }

        NodeList children = menuElement.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node childNode = children.item(i);
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                if (childNode.getNodeName().equals("menu-item")) {
                    Element menuItemElement = (Element) childNode;
                    this.readMenuItemElement(root, menuItemElement);
                } else if (childNode.getNodeName().equals("selection-map")) {
                    this.log.debug("Found selection-map for menu: " + root.getId());
                    Element mapElement = (Element) childNode;
                    this.readSelectionMapElement(root, mapElement);
                }
            }
        }
        return (root);
    }

    private void readMenuItemElement(MenuItem parent, Element menuElement) {
        this.log.debug("readMenuItemElement() called - parent = " + parent.getId());
        String id = menuElement.getAttribute("id");
        String patternForSelect = menuElement.getAttribute("patternForSelect");
        String name = menuElement.getAttribute("name");
        String href = menuElement.getAttribute("href");
        String absolute = menuElement.getAttribute("absolute");
        String permission = menuElement.getAttribute("permission");
        
        if (StringUtils.isNullOrBlank(id)) {
            id = name.hashCode() + "." + href.hashCode();
        }

        MenuItem child = parent.addItem(id, name, href);
        child.setPatternForSelect(patternForSelect);
        child.setPermissions(permission);
        
        if (this.log.isDebugEnabled()) {
            this.log.debug("readMenuItemElement() - parent = " + parent.getId() + "  child = " + id + "  absolute = "
                    + absolute);
            this.log.debug("readMenuItemElement() - patternForSelect = " + patternForSelect);
            this.log.debug("readMenuItemElement() - parent = " + parent.getId() + "  number of children = "
                    + parent.getChildList().size());
        }

        if ((absolute == null) || (absolute.trim().length() <= 0) || (absolute.trim().toLowerCase().equals("false"))) {
            this.log.debug("readMenuItemElement() - child has relative link");
            child.setRelativeLink(true);
        } else {
            this.log.debug("readMenuItemElement() - child DOES NOT have relative link");
            child.setRelativeLink(false);
        }
        
        NodeList children = menuElement.getChildNodes();
        this.log.debug("readMenuItemElement() - parent = " + parent.getId() + "  element child count = "
                + children.getLength());
        for (int i = 0; i < children.getLength(); i++) {
            Node childNode = children.item(i);
            if ((childNode.getNodeType() == Node.ELEMENT_NODE) && (childNode.getNodeName().equals("menu-item"))) {

                Element menuItemElement = (Element) childNode;
                this.readMenuItemElement(child, menuItemElement);
            }
        }
    }

    private void readSelectionMapElement(Menu menu, Element mapElement) {
        this.log
                .debug("readSelectionMapElement() - method to read mappings of http patterns to which 1st level menu item will be selected");
        NodeList children = mapElement.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node childNode = children.item(i);
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                if (childNode.getNodeName().equals("select")) {
                    Element entryElement = (Element) childNode;
                    String pattern = entryElement.getAttribute("pattern");
                    String menuId = entryElement.getAttribute("menuId");
                    menu.addSelectionMapEntry(pattern, menuId);

                    if (this.log.isDebugEnabled()) {
                        this.log.debug("Added selection mapping for menu: " + menu.getId() + "  Pattern = " + pattern
                                + "  menuItem Id = " + menuId);
                    }
                } else {
                    this.log.error("Unexpected element under selection-map element: " + childNode.getNodeName());
                }
            }
        }
    }

}
